msw で delay 使用時、Vitest においてテスト時の delay は 0 にする
msw を使うとき、ローカルで開発するためレスポンスに delay を使用することがあります。
// src/mocks/utils/handlers.ts export const handlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [ rest.get(client.v1.users.$path(), (_, res, ctx) => { return res(ctx.delay(), ctx.json(usersResponseDummy)); }), rest.get(client.v1.date.$path(), (_, res, ctx) => { return res(ctx.delay(2000), ctx.json(dateResponseDummy)); }), ];
このときに作成した RequestHandler
、上記のサンプルでは GraphQL ではなく REST なので RestHandler
ですが、こうしたハンドラを Storybook やテストで共有して使用する場合、delay があったとしてもおそらく Storybook ではとくに問題になりません。
ですがテストでは delay の分余計に時間がかかってしまうので、テスト時にはこのディレイを無効化したくなります。
Response transformer を使った方法
いくつか方法はありますが、ここでは Response transformer を使ってテスト時の delay を 0 にしてみます。
import { compose, context, ResponseTransformer, RestContext } from "msw"; export const delay = ( duration?: Parameters<RestContext["delay"]>[0] ): ResponseTransformer => { if (import.meta.env.MODE === "test") { return compose(context.delay(0)); } return compose(context.delay(duration)); };
今回は Vitest を使用しているので import.meta.env.MODE
が test
のとき、delay を 0 にしています。
これを使用する場合には次のように記述します。
import { delay } from "@/mocks/utils/delay"; export const handlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [ rest.get(client.v1.users.$path(), (_, res, ctx) => { return res(delay(), json(usersResponseDummy)); }), rest.get(client.v1.date.$path(), (_, res, ctx) => { return res(delay(2000), ctx.json(dateResponseDummy)); }), ];
作成した delay
のテストを書く
msw
の delay
をモックして、実行時の引数が正しく渡せているかをテストします。
Vitest の Mock は 公式ドキュメントの Cheat Sheetがわかりやすいです。module をモックしたいので Modules の項目を参考に、delay
をモックします。
vi.mock("msw", async () => { const actual = await vi.importActual<typeof import("msw")>("msw"); return { ...actual, context: { ...actual.context, delay: vi.fn(), }, }; });
ここでは delay
のみをモックすればいいので、importActual
を使っています。jest だと requireActual
なので、私はそれに気づかずちょっとハマってました。
次にテストごとに環境変数を変更する方法ですが、こちらも先程の公式ドキュメントの Cheat Sheetに記載されています。
const originalViteEnvMode = import.meta.env.MODE; beforeEach(() => { import.meta.env.MODE = originalViteEnvMode; });
開発時とテスト時にそれぞれ delay
の引数が正しく渡せているかを確認するテストをしてみます。
describe("delay", () => { it("開発時に delay が指定された時間になること", () => { import.meta.env.MODE = "development"; const delayMock = (context as Mocked<typeof context>).delay; delay(100); expect(delayMock.mock.calls.length).toBe(1); expect(delayMock.mock.calls[0][0]).toBe(100); }); it("テスト時に delay が 0 になること", () => { const delayMock = (context as Mocked<typeof context>).delay; delay(100); expect(delayMock.mock.calls.length).toBe(1); expect(delayMock.mock.calls[0][0]).toBe(0); }); });
その他の方法
参考にした discussions のリンク を見ていただけるとわかるのですが、別の方法としてはシンプルに ctx.delay()
内に数値を指定するとき、引数に関数でラップした数値を ctx.delay(ms(100))
として ms()
がテストのときに 0 を戻すようにしてあげる方法があります。